using System;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Xml;
using Autofac;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using uTILLIty.Rave.Contract.Configuration;
using uTILLIty.Rave.Contract.Exception;
using uTILLIty.Rave.Contract.Job;
using uTILLIty.Rave.Core.StorageAdapter;

namespace uTILLIty.Rave.Core.Manager
{
	[TestClass]
	public class ConfigurationManagerTests : TestBase
	{
		[TestMethod, DeploymentItem("Samples\\NVelocity.xml")]
		public void LoadAndParse()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[]
			{
				new MergeVariable("Now", DateTime.Now),
				MergeVariable.Create("Guid", Guid.NewGuid().ToString()),
				MergeVariable.Create("ValueToXmlEscape", "Stan & Oley")
			};
			var job = new Job(new FileInfo("NVelocity.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);

			Assert.AreEqual(@".\Test\" + DateTime.Now.Year, config.RootDirectory);
			Assert.AreEqual(parseValues[1].Value, config.Members.Single(m => m.ID == "PrjOwner").Identifier);
		}

		[TestMethod, DeploymentItem("Samples\\SampleProject.xml")]
		public void LoadAndParseSampleProjectWithValidation()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[]
			{
				MergeVariable.Create("PrjID", "001"), 
				MergeVariable.Create("PrjName", "MyProject"), 
				MergeVariable.Create("owners[|]", "Mark"), 
				MergeVariable.Create("members[|]", "Mike|Nancy|John"), 
				MergeVariable.Create("BaseDN", "DN=my,DN=name")
			};
			var job = new Job(new FileInfo("SampleProject.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);
			var prjMembersCreationConfig = config.Members.Single(m => m.ID == "PrjMembers")
				.CreationConfiguration.Data.ToObject<GroupCreationConfiguration>();
			Assert.AreEqual(3, prjMembersCreationConfig.Members.Length);
		}

		[TestMethod, DeploymentItem("Samples\\SampleProject2.xml")]
		public void LoadAndParseSampleProjectWithValidationEmptyDirectoryName()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[]
			{
				MergeVariable.Create("PrjName", "")
			};
			var job = new Job(new FileInfo("SampleProject2.xml").FullName, parseValues);
			InitTraceSource(job);
			try
			{
				target.LoadAndParse(job);
				Assert.Fail("Validation error expected!");
			}
			catch (ConfigurationErrorException)
			{
			}
		}

		[TestMethod, DeploymentItem("Samples\\SampleProject2.xml")]
		public void LoadAndParseSampleProjectWithValidationDirectoryName()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[]
			{
				MergeVariable.Create("PrjName", "123")
			};
			var job = new Job(new FileInfo("SampleProject2.xml").FullName, parseValues);
			InitTraceSource(job);
			target.LoadAndParse(job);
		}

		[TestMethod, DeploymentItem("Samples\\ApplyActiveDirectorySimple.xml")]
		public void ApplyActiveDirectorySimple()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[]
			{
			  new MergeVariable("Guid", Guid.NewGuid()),
				MergeVariable.Create("Project", "ApplyActiveDirectorySimple")
			};

			var job = new Job(new FileInfo("ApplyActiveDirectorySimple.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);
			target.Apply(job, config, false);

			var targetPath = new DirectoryInfo(Path.Combine(config.RootDirectory, config.Directories[0].Name));
			Assert.IsTrue(targetPath.Exists, "Dir {0} does not exist", targetPath.FullName);
		}

		[TestMethod, DeploymentItem("Samples\\TwoAclsSameMember.xml")]
		public void TwoAclsSameMemberRerun()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[] { new MergeVariable("Root", "TwoAclsSameMemberNewDirectory") };

			var job = new Job(new FileInfo("TwoAclsSameMember.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);
			target.Apply(job, config, false);

			var targetPath = new DirectoryInfo(Path.Combine(config.RootDirectory, config.Directories[0].Name));
			Assert.IsTrue(targetPath.Exists, "Dir {0} does not exist", targetPath.FullName);

			target.Apply(job, config, false);

			targetPath.Refresh();
			var acls = targetPath.GetAccessControl(AccessControlSections.Access)
				.GetAccessRules(true, false, typeof(SecurityIdentifier)).Cast<FileSystemAccessRule>().ToArray();
			foreach (var acl in acls)
				PrintAcl(acl);
			Assert.AreEqual(2, acls.Length);
		}

		[TestMethod, DeploymentItem("Samples\\TwoAclsSameMember.xml")]
		public void TwoAclsSameMemberNewDirectory()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[] { new MergeVariable("Root", "TwoAclsSameMemberNewDirectory") };

			var job = new Job(new FileInfo("TwoAclsSameMember.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);
			target.Apply(job, config, false);

			var targetPath = new DirectoryInfo(Path.Combine(config.RootDirectory, config.Directories[0].Name));
			Assert.IsTrue(targetPath.Exists, "Dir {0} does not exist", targetPath.FullName);

			targetPath.Refresh();
			var acls = targetPath.GetAccessControl(AccessControlSections.Access)
				.GetAccessRules(true, false, typeof(SecurityIdentifier)).Cast<FileSystemAccessRule>().ToArray();
			foreach (var acl in acls)
				PrintAcl(acl);
			Assert.AreEqual(2, acls.Length);
		}

		[TestMethod, DeploymentItem("Samples\\TwoAclsSameMember.xml")]
		public void TwoAclsSameMemberApplyDenial()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[] { new MergeVariable("Root", "TwoAclsSameMemberApplyDenial") };

			var job = new Job(new FileInfo("TwoAclsSameMember.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);
			var entry = new SecurityEntry
				{
					MemberType = MemberType.User,
					InheritanceFlags = InheritanceFlags.None,
					MemberID = config.Members.Single().ID,
					AccessControlType = AccessControlType.Deny,
					Rights = "WriteExtendedAttributes, WriteAttributes, Delete"
				};
			config.Directories.Single().SecurityEntries = config.Directories.Single().SecurityEntries.ToList().Concat(new[] { entry }).ToArray();
			target.Apply(job, config, false);

			var targetPath = new DirectoryInfo(Path.Combine(config.RootDirectory, config.Directories[0].Name));
			Assert.IsTrue(targetPath.Exists, "Dir {0} does not exist", targetPath.FullName);

			targetPath.Refresh();
			var acls = targetPath.GetAccessControl(AccessControlSections.Access)
				.GetAccessRules(true, false, typeof(SecurityIdentifier)).Cast<FileSystemAccessRule>().ToArray();
			foreach (var acl in acls)
				PrintAcl(acl);
			Assert.IsTrue(acls.Any(a => a.AccessControlType == AccessControlType.Deny), "Denial ACL not found!");
			Assert.AreEqual(3, acls.Length);
		}

		[TestMethod, DeploymentItem("Samples\\TwoAclsSameMember.xml")]
		public void TwoAclsSameMemberUpdate()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[] { new MergeVariable("Root", "TwoAclsSameMemberUpdate") };

			var job = new Job(new FileInfo("TwoAclsSameMember.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);

			var targetPath = new DirectoryInfo(Path.Combine(config.RootDirectory, config.Directories[0].Name));
			if (targetPath.Exists)
				targetPath.Delete(true);
			targetPath.Create();

			const FileSystemRights fsRights = FileSystemRights.ReadAttributes | FileSystemRights.ReadExtendedAttributes | FileSystemRights.ReadPermissions;
			var right = new FileSystemAccessRule(new SecurityIdentifier("S-1-5-7"), fsRights, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow);
			var tmp = targetPath.GetAccessControl(AccessControlSections.Access);
			tmp.AddAccessRule(right);
			targetPath.SetAccessControl(tmp);

			target.Apply(job, config, false);

			var acls = targetPath.GetAccessControl(AccessControlSections.Access)
				.GetAccessRules(true, false, typeof(SecurityIdentifier)).Cast<FileSystemAccessRule>().ToArray();
			foreach (var acl in acls)
				PrintAcl(acl);
			Assert.AreEqual(2, acls.Length);
		}

		[TestMethod, DeploymentItem("Samples\\TwoAclsSameMember.xml")]
		public void TwoAclsSameMemberRemoveDenial()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[] { new MergeVariable("Root", "TwoAclsSameMemberUpdate") };

			var job = new Job(new FileInfo("TwoAclsSameMember.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);
			config.Directories[0].RemoveAllOtherSecurityEntries = true;

			var targetPath = new DirectoryInfo(Path.Combine(config.RootDirectory, config.Directories[0].Name));
			if (targetPath.Exists)
				targetPath.Delete(true);
			targetPath.Create();

			const FileSystemRights fsRights = FileSystemRights.ReadAttributes | FileSystemRights.ReadExtendedAttributes | FileSystemRights.ReadPermissions;
			var right = new FileSystemAccessRule(new SecurityIdentifier("S-1-5-7"), fsRights, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Deny);
			var tmp = targetPath.GetAccessControl(AccessControlSections.Access);
			tmp.AddAccessRule(right);
			targetPath.SetAccessControl(tmp);


			target.Apply(job, config, false);

			var acls = targetPath.GetAccessControl(AccessControlSections.Access)
				.GetAccessRules(true, false, typeof(SecurityIdentifier)).Cast<FileSystemAccessRule>().ToArray();
			foreach (var acl in acls)
				PrintAcl(acl);
			Assert.AreEqual(2, acls.Length);
		}

		[TestMethod, DeploymentItem("Samples\\TwoAclsSameMember.xml")]
		public void TwoAclsSameMemberKeepDenial()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[] { new MergeVariable("Root", "TwoAclsSameMemberUpdate") };

			var job = new Job(new FileInfo("TwoAclsSameMember.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);
			config.Directories[0].RemoveAllOtherSecurityEntries = false;

			var targetPath = new DirectoryInfo(Path.Combine(config.RootDirectory, config.Directories[0].Name));
			if (targetPath.Exists)
				targetPath.Delete(true);
			targetPath.Create();

			const FileSystemRights fsRights = FileSystemRights.ReadAttributes | FileSystemRights.ReadExtendedAttributes | FileSystemRights.ReadPermissions;
			var right = new FileSystemAccessRule(new SecurityIdentifier("S-1-5-7"), fsRights, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Deny);
			var tmp = targetPath.GetAccessControl(AccessControlSections.Access);
			tmp.AddAccessRule(right);
			targetPath.SetAccessControl(tmp);


			target.Apply(job, config, false);

			var acls = targetPath.GetAccessControl(AccessControlSections.Access)
				.GetAccessRules(true, false, typeof(SecurityIdentifier)).Cast<FileSystemAccessRule>().ToArray();
			foreach (var acl in acls)
				PrintAcl(acl);
			Assert.AreEqual(3, acls.Length);
		}

		private static void PrintAcl(FileSystemAccessRule acl)
		{
			Console.WriteLine("IdentityReference: {0}", acl.IdentityReference);
			Console.WriteLine("    AccessControlType: {0}", acl.AccessControlType);
			Console.WriteLine("    FileSystemRights:  {0}", acl.FileSystemRights);
			Console.WriteLine("    InheritanceFlags:  {0}", acl.InheritanceFlags);
			Console.WriteLine("    IsInherited:       {0}", acl.IsInherited);
			Console.WriteLine("    PropagationFlags:  {0}", acl.PropagationFlags);
		}

		[TestMethod, DeploymentItem("Samples\\UnknownAdapter.xml")]
		[ExpectedException(typeof(NotSupportedException))]
		public void ApplyActiveDirectoryUnknownAdapter()
		{
			ConfigurationManager target = GetConfigurationManager();
			var parseValues = new[]
			{
			  new MergeVariable("Guid", Guid.NewGuid()),
				MergeVariable.Create("Project", "UnknownAdapter")
			};

			var job = new Job(new FileInfo("UnknownAdapter.xml").FullName, parseValues);
			InitTraceSource(job);
			target.LoadAndParse(job);
			Assert.Fail("Should fail!");
		}

		/// <summary>
		/// Requires the file 'LdapCredentials.xml' in the Core.Tests root directory with the following syntax:
		/// </summary>
		/// <remarks>
		/// XML file not included and credentials not hard-coded, as this is sensitive information
		/// </remarks>
		/// <example>
		/// <LdapCredentials>
		/// 	<CreationBaseDn>OU=_Trash,DC=Some,DC=Domain</CreationBaseDn>
		/// 	<LdapConnectionString>LDAP://DC=Some,DC=Domain</LdapConnectionString>
		/// 	<Username>admin</Username>
		/// 	<Password>xxx</Password>
		/// </LdapCredentials>
		/// </example>
		[TestMethod, DeploymentItem("Samples\\ApplyActiveDirectoryCreateGroup.xml")]
		public void ApplyActiveDirectoryCreateGroup()
		{
			ConfigurationManager target = GetConfigurationManager();
			var node = new XmlDocument();
			node.Load(@"..\..\..\Core.Tests\LdapCredentials.xml");
			//node.Load(@"LdapCredentials.xml");
			var creationCredentials = node.DocumentElement.ToObject<LdapCredentials>();
			var parseValues = new[]
                          {
                            MergeVariable.Create("User", GetCurrentUserName()),
                          	new MergeVariable("Guid", Guid.NewGuid()),
                            MergeVariable.Create("Project", "ApplyActiveDirectoryCreateGroup"),
                            //MergeVariable.Create("BaseDN", GetBaseDn()),
                            MergeVariable.Create("BaseDN", creationCredentials.CreationBaseDn),
                            new MergeVariable("Now", DateTime.Now),
                            MergeVariable.Create("LDAP",creationCredentials.LdapConnectionString),
                            MergeVariable.Create("USER",creationCredentials.Username),
                            MergeVariable.Create("PWD", creationCredentials.Password)
                          };

			var job = new Job(new FileInfo("ApplyActiveDirectoryCreateGroup.xml").FullName, parseValues);
			InitTraceSource(job);
			var config = target.LoadAndParse(job);
			target.Apply(job, config, false);

			var targetPath = new DirectoryInfo(Path.Combine(config.RootDirectory, config.Directories[0].Name));
			Assert.IsTrue(targetPath.Exists, "Dir {0} does not exist", targetPath.FullName);
		}

		private static string GetCurrentUserName()
		{
			// ReSharper disable PossibleNullReferenceException
			return WindowsIdentity.GetCurrent().Name.Split('\\')[1];
			// ReSharper restore PossibleNullReferenceException
		}

		private ConfigurationManager GetConfigurationManager()
		{
			var target = Container.Resolve<ConfigurationManager>();
			target.OnAfterMerge += (j, d) =>
			{
				TestContext.WriteLine(d);
				return d;
			};
			return target;
		}

		/*
				private static string GetBaseDn()
				{
					var entry = new DirectoryEntry();
					var baseDn = entry.Properties["distinguishedName"][0].ToString();
					return baseDn;
				}
		*/
	}
}